{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据增强（互联模块）(DataConnectionModule)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### LEDVR工作流"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![LEDVR工作流](./LEDVRWorkflow.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 以WebBaseLoader展示文档加载\n",
    "from langchain.document_loaders import WebBaseLoader\n",
    "loader = WebBaseLoader(\"https://zh.wikipedia.org/zh-cn/LangChain\")\n",
    "data = loader.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.embeddings.openai.OpenAIEmbeddings` was deprecated in langchain-community 0.1.0 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAIEmbeddings`.\n",
      "  warn_deprecated(\n"
     ]
    }
   ],
   "source": [
    "# 使用嵌入模型包装器将切割后的文本转换为向量数据\n",
    "from langchain.embeddings.openai import OpenAIEmbeddings\n",
    "embedding = OpenAIEmbeddings(\n",
    "    openai_api_key = os.environ[\"OPENAI_API_KEY\"]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建文档分隔器以将从WebBaseLoader返回的HTML数据进行拆分\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_spliter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size = 500, # 设置一个块的大小\n",
    "    chunk_overlap = 0 # 设置是否允许块重叠\n",
    ")\n",
    "\n",
    "splits = text_spliter.split_documents(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入FAISS向量存储库\n",
    "from langchain.vectorstores import FAISS\n",
    "vectordb = FAISS.from_documents(\n",
    "    documents = splits,\n",
    "    embedding = embedding\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n",
      "  warn_deprecated(\n"
     ]
    }
   ],
   "source": [
    "# 实例化一个检索器\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "from langchain.retrievers.multi_query import MultiQueryRetriever\n",
    "\n",
    "question = \"LangChain支持哪些编程语言？\"\n",
    "# 初始化LLM\n",
    "llm = ChatOpenAI(\n",
    "    openai_api_key = os.environ['OPENAI_API_KEY'],\n",
    ")\n",
    "# 初始化多查询检索器\n",
    "retriever_from_llm = MultiQueryRetriever.from_llm(\n",
    "    llm = llm,\n",
    "    # 使用.as_retriever()将向量数据库转换为一个检索器\n",
    "    retriever = vectordb.as_retriever()\n",
    ")\n",
    "# 使用.get_relevant_documents()方法获取相关文档\n",
    "docs = retriever_from_llm.get_relevant_documents(question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "page_content='此条目可参照英语维基百科相应条目来扩充。若您熟悉来源语言和主题，请协助参考外语维基百科扩充条目。请勿直接提交机械翻译，也不要翻译不可靠、低品质内容。依版权协议，译文需在编辑摘要注明来源，或于讨论页顶部标记{{Translated page}}标签。\\nLangChain 是一个应用框架，旨在简化使用大型语言模型的应用程序。作为一个语言模型集成框架，LangChain 的用例与一般语言模型的用例有很大的重叠。 重叠范围包括文档分析和总结摘要, 代码分析和聊天机器人。[1]\\nLangChain提供了一个标准接口，用于将不同的语言模型（LLM）连接在一起，以及与其他工具和数据源的集成。LangChain还为常见应用程序提供端到端链，如聊天机器人、文档分析和代码生成。 LangChain是由Harrison Chase于2022年10月推出的开源软件项目。它已成为LLM开发中最受欢迎的框架之一。LangChain支持Python和JavaScript语言，并与各种LLM一起使用，如GPT-4、BERT和T5。[2]' metadata={'source': 'https://zh.wikipedia.org/zh-cn/LangChain', 'title': 'LangChain - 维基百科，自由的百科全书', 'language': 'zh-Hans-CN'}\n"
     ]
    }
   ],
   "source": [
    "print(docs[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 嵌入包装器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LangChain提供了一个预先封装的`Embedding`类，使用方法是：实例化该嵌入包装器并传递对应的参数，例如OpenAIEmbeddings初始化时只需要传递`api_key`参数即可。\n",
    "\n",
    "```python\n",
    "from langchain.embedding import OpenAIEmbeddings\n",
    "\n",
    "embeddings = OpenAIEmbeddings(api_key=\"your_api_key\") # 通常会调用OpenAI的Text-embedding-ada-002-v2模型\n",
    "```\n",
    "\n",
    "通常这类嵌入包装器提供两个方法：\n",
    "- `embed_documents`：接受一组文本作为输入并返回它们的嵌入向量\n",
    "- `embed_query`：接受一个文本并返回器嵌入向量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重新初始化一个OpenAI嵌入包装器\n",
    "from langchain.embeddings.openai import OpenAIEmbeddings\n",
    "embedding_model = OpenAIEmbeddings(\n",
    "    openai_api_key = os.environ[\"OPENAI_API_KEY\"]\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5, 1536)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 嵌入文本\n",
    "embeddings = embedding_model.embed_documents(\n",
    "    [\n",
    "        \"Hi there!\",\n",
    "        \"Oh,hello!\",\n",
    "        \"What's your name?\",\n",
    "        \"My friends call me World\",\n",
    "        \"Hello World!\"\n",
    "    ]\n",
    ")\n",
    "\n",
    "len(embeddings),len(embeddings[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LangChain提供了以下可用的嵌入模型包装器类型：\n",
    "- 自然语言模型嵌入：这类嵌入包括`OpenAIEmbeddings`、`HuggingFaceEmbeddings`、`HuggingFaceHubEmbeedings`、`HuggingFaceInstructEmbeddings`、`SelfHostedHuggingFaceEmbeddings`和`SelfHostedHuggingFaceInstructEmbeddings`等。专为OpenAI和HuggingFace等自然语言模型提供的嵌入包装器。\n",
    "- AI平台和云服务嵌入：这类嵌入主要依托AI平台和云服务器的能力进行文本嵌入，包括ElasticSearch、SagemakerEndpoint和DeepInfra等。这类嵌入的主要特点是能够利用云端算力进行大规模文本嵌入。\n",
    "- 单独设计的嵌入模型：用于处理特定结构的文本，主要包括`AlephAlpha`的`AsymmetricSemanticEmbeddin`和`SymmetricSemanticEmbedding`等。这类嵌入适用于处理结构不同或相似的文本。\n",
    "- 自托管嵌入：这类嵌入一般适用于用户自行部署和管理的场景，如`SelfHostedEmbeddings`。\n",
    "- 仿真和测试用嵌入：例如，`FakeEmbeddings`一般用于测试或模拟场景，不涉及实际的嵌入计算。\n",
    "- 其他类型：此外，LangChain还支持一些其他类型的嵌入方式，如`Cohere`、`LlamaCpp`、`ModelScope(阿里魔搭)`、`TensorflowHub`、`MosaicMLInstructor`、`MiniMax`、`Berock`、`DashScope`和`Embaas`等。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 文档转换器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.  \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution. \\n\\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \\n\\nSix days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \\n\\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \\n\\nHe met the Ukrainian people. \\n\\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载字符文本分割器\n",
    "with open(\"./state_of_the_union.txt\",\"r\") as f:\n",
    "    state_of_the_union = f.read()\n",
    "\n",
    "from langchain.text_splitter import CharacterTextSplitter\n",
    "text_spliter = CharacterTextSplitter(\n",
    "    chunk_size = 1000,\n",
    "    chunk_overlap = 200\n",
    ")\n",
    "\n",
    "texts = text_spliter.create_documents([state_of_the_union])\n",
    "\n",
    "texts[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='#include <stdio.h>'),\n",
       " Document(page_content='int main() {\\n    printf(\"Hello, World!\");\\n    return 0;\\n}')]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 文档加载的第二种方式：代码分隔\n",
    "from langchain.text_splitter import (\n",
    "    RecursiveCharacterTextSplitter,\n",
    "    Language # 此扩展支持很多种编程语言，甚至是Latex和makrdown\n",
    ")\n",
    "\n",
    "# 创建模板语言代码\n",
    "code = \"\"\"\n",
    "#include <stdio.h>\n",
    "int main() {\n",
    "    printf(\"Hello, World!\");\n",
    "    return 0;\n",
    "}\n",
    "\"\"\"\n",
    "\n",
    "# 分隔代码\n",
    "cpp_spliter = RecursiveCharacterTextSplitter.from_language(\n",
    "    language = Language.CPP,\n",
    "    chunk_size = 60,\n",
    "    chunk_overlap = 0\n",
    ")\n",
    "\n",
    "cpp_docs = cpp_spliter.create_documents([code])\n",
    "cpp_docs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Markdown分隔\n",
    "\n",
    "# 定义分隔的Markdown标题\n",
    "headers_to_split_on = [\n",
    "    (\"#\",\"Header 1\"),\n",
    "    (\"##\",\"Header 2\")\n",
    "]\n",
    "\n",
    "# 创建样本Markdown文档\n",
    "markdown = \"\"\"\n",
    "# Header 1\n",
    "This is some text under header 1\n",
    "## Header 2\n",
    "This is some text under header 2\n",
    "\"\"\"\n",
    "\n",
    "# 创建Markdown分隔器\n",
    "from langchain.text_splitter import MarkdownHeaderTextSplitter\n",
    "markdown_spliter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
    "md_header_splits = markdown_spliter.split_text(markdown)\n",
    "\n",
    "# 导入递归字分割器\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_spliter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size = 250,\n",
    "    chunk_overlap = 30\n",
    ")\n",
    "\n",
    "# 分隔\n",
    "splits = text_spliter.split_documents(md_header_splits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='This is some text under header 1', metadata={'Header 1': 'Header 1'}),\n",
       " Document(page_content='This is some text under header 2', metadata={'Header 1': 'Header 1', 'Header 2': 'Header 2'})]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "splits"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 词元切割(TokenSplit)\n",
    "\n",
    "LangChain提供了以下几种可用的基于Token进行分割的分割器：\n",
    "- Tiktoken标记切割器：基于OpenAI的字节对编码(Byte Pair Encoding, BPE)算法进行分割，适用于OpenAI的GPT-3/4 等模型。\n",
    "- SpacyTextSpliter：基于Python和Cython实现的自然语言处理工具包，适用于Spacy模型。NLTK的替代方案。\n",
    "- SentenceTransformersTokenTextSpliter：专门用于处理转换模型的专用文本切割器。\n",
    "- HuggingFace标记切割器：使用HuggingFaceHub上来进行单独的切割。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.  \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution.'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 基于GPT2 Tokenizer进行分割\n",
    "from transformers import GPT2Tokenizer\n",
    "\n",
    "# 加载tokenizer\n",
    "tokenizer = GPT2Tokenizer.from_pretrained(\"gpt2\")\n",
    "\n",
    "# 加载文本\n",
    "with open(\"./state_of_the_union.txt\",\"r\") as f:\n",
    "    state_of_the_union = f.read()\n",
    "\n",
    "# 从LangChain加载字符分割器\n",
    "from langchain.text_splitter import CharacterTextSplitter\n",
    "# 使用.from_huggingface_tokenizer()函数来加载Tokenizer\n",
    "text_spliter = CharacterTextSplitter.from_huggingface_tokenizer(\n",
    "    tokenizer = tokenizer,\n",
    "    chunk_size = 100,\n",
    "    chunk_overlap = 0\n",
    ")\n",
    "\n",
    "texts = text_spliter.split_text(state_of_the_union)\n",
    "\n",
    "texts[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 向量存储库(VectorDatabase)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 初始化FAISS\n",
    "from langchain.document_loaders import TextLoader\n",
    "from langchain.embeddings.openai import OpenAIEmbeddings\n",
    "from langchain.text_splitter import CharacterTextSplitter\n",
    "from langchain.vectorstores import FAISS\n",
    "\n",
    "# 加载文本\n",
    "raw_documents = TextLoader(\"./state_of_the_union.txt\").load()\n",
    "text_spliter = CharacterTextSplitter(\n",
    "    chunk_size = 1000,\n",
    "    chunk_overlap = 0\n",
    ")\n",
    "documents = text_spliter.split_documents(raw_documents)\n",
    "db = FAISS.from_documents(\n",
    "    documents,\n",
    "    OpenAIEmbeddings() \n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \\n\\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \\n\\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \\n\\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 使用FAISS.similarity_search()函数来搜索相似文档\n",
    "query = \"What did the president say about Ketanji Brown Jackson\"\n",
    "docs = db.similarity_search(query)\n",
    "docs[0].page_content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 常见的向量存储库搜索方法\n",
    "- 相似度搜索：\n",
    "```python\n",
    "simlarity_search(query:str,k:int = 4) -> List[Document]\n",
    "```\n",
    "这个方法接受一个字符串查询和一个整数k作为参数，返回与查询最相似的k个文档。\n",
    "- 通过向量进行的相似度搜索\n",
    "```python\n",
    "simlarity_search_by_vector(embedding:List[float],k:int = 4) -> List[Document]\n",
    "```\n",
    "这个方法接受一个嵌入向量和一个整数k作为参数，返回与嵌入向量最相似的k个文档。\n",
    "- 最大边缘相似度搜索\n",
    "```python\n",
    "max_margin_simlarity_search(query:str,k:int = 4,fetch_k:int = 20,lambda_mult:float = 0.5) -> List[Document]\n",
    "```\n",
    "这个方法使用最大边际相关性算法返回选择的文档，query是要搜索的字符串，k是要返回的文档数量，fetch_k是传递给最大边际相关性算法的文档数量。lambda_mult是设置结果多样性的系数，默认为0.5\n",
    "- 通过向量实现的最大边缘相似度搜索\n",
    "```python\n",
    "max_margin_simlarity_search_by_vector(embedding:List[float],k:int = 4,fetch_k:int = 20,lambda_mult:float = 0.5) -> List[Document]\n",
    "```\n",
    "这个方法与上述的方法类似，只是接受一个嵌入向量作为参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 检索器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![HowRetrieverWork](./HowRetrieverWork.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 检索器的使用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载文本\n",
    "loader = TextLoader(\"./state_of_the_union.txt\")\n",
    "documents = loader.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建字符文本分割器\n",
    "text_spliter = CharacterTextSplitter(\n",
    "    chunk_size = 1000,\n",
    "    chunk_overlap = 0\n",
    ")\n",
    "\n",
    "texts = text_spliter.split_documents(documents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建OpenAI嵌入\n",
    "embeddings = OpenAIEmbeddings()\n",
    "from langchain.vectorstores import Chroma\n",
    "docsearch = Chroma.from_documents(\n",
    "    texts,embeddings\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.llms.openai.OpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAI`.\n",
      "  warn_deprecated(\n"
     ]
    }
   ],
   "source": [
    "# 创建QA链\n",
    "from langchain.llms import OpenAI\n",
    "from langchain.chains.retrieval_qa.base import RetrievalQA\n",
    "qa = RetrievalQA.from_chain_type(\n",
    "    llm = OpenAI(\n",
    "        openai_api_key = os.environ[\"OPENAI_API_KEY\"]\n",
    "    ),\n",
    "    chain_type = \"stuff\",\n",
    "    retriever = docsearch.as_retriever()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 检索器的类型\n",
    "- 自查询检索器：这种检索器适用于通过自然语言查询来检索具有一定结构或元数据的文档的场景。例如电商网站的搜索引擎。\n",
    "- 时间加权向量存储检索器：这种检索器适用于信息的新旧程度对查询结果影响交的场景。比如，在新闻检索中，用户通常更关心最新的新闻，因此检索器需要根据新闻的发布时间来对结果进行排序。\n",
    "- 向量存储支持的检索器：这种检索器适用于需要基于语义相似度进行查询的场景。比如，在问答系统中，用户的问题可能会有很多种表达方式，只有理解了问题的语义，才能找到正确的答案。\n",
    "- 网络研究检索器：这种检索器适用于需要从网络上获得最新数据的场景。比如，用户可能想要获取关于一个热点事件的最新信息，此时检索器可以直接从网络上进行检索，以便获取到最新的消息。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
